/* 
 * Copyright (c) 2012 JSquirrelEngine
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in 
 * the Software without restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the
 * Software, and to permit persons to whom the Software is furnished to do so, subject
 * to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * 
 */

package org.jsquirrel.rendering.LWJGLimpl;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.color.ColorSpace;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.awt.image.ComponentColorModel;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.Raster;
import java.awt.image.WritableRaster;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.IntBuffer;
import java.util.HashMap;
import java.util.Hashtable;

import javax.imageio.ImageIO;

import org.jsquirrel.rendering.TextureLoader;
import org.lwjgl.opengl.GL11;

/**
 * 
 * @author Juan David Adarve Bermudez
 *
 */
public class LWJGL_TextureLoader extends TextureLoader {
	
	//*********************************************************************************************
	// INSTANCE ATTRIBUTES
	//*********************************************************************************************
	
	/**
	 * Graphics to which this instance of TextureLoader belongs.
	 * It cannot be changed once it is assigned, at object
	 * construction.
	 * FIXME: Is it really needed?
	 */
	private final LWJGL_Graphics graphics;
	
	private HashMap<Integer, LWJGL_Texture> textureMap;
	
	
	public LWJGL_TextureLoader(LWJGL_Graphics graphics) {
		super();
		this.graphics = graphics;
		textureMap = new HashMap<Integer, LWJGL_Texture>();
	}

	//*********************************************************************************************
	// PUBLIC METHODS
	//*********************************************************************************************
	
	public final LWJGL_Texture getTexture(int textureID) {
		return textureMap.get(textureID);
	}
	
	
	@Override
	public int createTexture(InputStream imageSource) throws IOException {
		
		int texID = -1;
		int texFormat;
		
		// image reading from the input stream
		BufferedImage buffImage = ImageIO.read(new BufferedInputStream(imageSource));
		
		int imgChannels = buffImage.getColorModel().getNumComponents();
		int imgWidth = buffImage.getWidth();
		int imgHeight = buffImage.getHeight();
		
		// power of 2 greater than imgWidth
		int upperWidth = (int)Math.pow(2, Math.ceil(Math.log10(imgWidth)/Math.log10(2)));
		// power of 2 less than imgWidth
		int lowerWidth = (int)Math.pow(2, Math.floor(Math.log10(imgWidth)/Math.log10(2)));
		// the texture width is equal to the closest power of 2
		int texWidth =  upperWidth - imgWidth < imgWidth - lowerWidth? upperWidth : lowerWidth;
		
		// power of 2 greater than imgHeight
		int upperHeight = (int)Math.pow(2, Math.ceil(Math.log10(imgHeight)/Math.log10(2)));
		// power of 2 less than imgHeight
		int lowerHeight = (int)Math.pow(2, Math.floor(Math.log10(imgHeight)/Math.log10(2)));
		// the texture height is the closest power of 2
		int texHeight = upperHeight - imgHeight < imgHeight - lowerHeight? upperHeight : lowerHeight;
		
		
		// NEW BUFFERED IMAGE WITH CORRECTED SIZE
		WritableRaster raster = Raster.createInterleavedRaster(DataBuffer.TYPE_BYTE,
				texWidth, texHeight, imgChannels, new Point(0,0));
		
		ColorModel colorModel;
		BufferedImage texImage;
		Graphics g;
		switch(imgChannels) {
			
			// TODO: add monochrome textures support
		
			// RGB texture
			case 3:
				texFormat = GL11.GL_RGB;
				colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB), 
						false,
						false,
						ComponentColorModel.OPAQUE,
						DataBuffer.TYPE_BYTE);
				
				// texture image with corrected size
				texImage = new BufferedImage(colorModel, raster, false, new Hashtable<String, Object>());
				
				g = texImage.getGraphics();
				g.drawImage(buffImage, 0, 0, texImage.getWidth(), texImage.getHeight(), Color.WHITE, null);
				
				break;
			// RGBA texture
			case 4:
				texFormat = GL11.GL_RGBA;
				colorModel = new ComponentColorModel(ColorSpace.getInstance(ColorSpace.CS_sRGB),
						true,
						false,
						ComponentColorModel.TRANSLUCENT,
						DataBuffer.TYPE_BYTE);
				// texture image with corrected size
				texImage = new BufferedImage(colorModel, raster, false, new Hashtable<String, Object>());
				
				g = texImage.getGraphics();
				// scale the image and set the BG color to translucent
				g.drawImage(buffImage, 0, 0, texImage.getWidth(), texImage.getHeight(), new Color(1, 1, 1, 1), null);
				
				break;
			// other number of color channels
			default:
				throw new RuntimeException("LWJGL_TextureLoader.createTexture(): unsupported color channel number: "  + imgChannels);
		}
		
		// here, texImage is ready to be transfered to OpenGL
		
		// byte data of texImage
		byte [] imageBytes = ((DataBufferByte) texImage.getRaster().getDataBuffer()).getData();	
		ByteBuffer buffer = ByteBuffer.allocateDirect(imageBytes.length);
		buffer.order(ByteOrder.nativeOrder());
		buffer.put(imageBytes, 0, imageBytes.length);
		buffer.flip();
		
				
		// get a new textureID
		texID = generateTextureID();
		GL11.glBindTexture(GL11.GL_TEXTURE_2D, texID);
		GL11.glTexImage2D(GL11.GL_TEXTURE_2D, 0, texFormat,
				texWidth, texHeight, 0, texFormat, GL11.GL_UNSIGNED_BYTE, buffer);
				
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MIN_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_MAG_FILTER, GL11.GL_LINEAR);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_S, GL11.GL_CLAMP);
		GL11.glTexParameterf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_WRAP_T, GL11.GL_CLAMP);
		
		GL11.glTexEnvf(GL11.GL_TEXTURE_2D, GL11.GL_TEXTURE_ENV_MODE, GL11.GL_DECAL);		
		GL11.glEnable(GL11.GL_TEXTURE_2D);
		
		// create a LWJGL_Texture to store the texture data
		LWJGL_Texture textureObject = new LWJGL_Texture(texID, texWidth, texHeight, buffer, texFormat);
		this.textureMap.put(textureObject.getID(), textureObject);
		
		// cleaning
		imageSource.close();
		
			
		return texID;
	}
	
	//*********************************************************************************************
	// PRIVATE METHODS
	//*********************************************************************************************
	
	private int generateTextureID() {
		
		ByteBuffer byteB = ByteBuffer.allocateDirect(4);
		byteB.order(ByteOrder.nativeOrder());
		IntBuffer intB = byteB.asIntBuffer();
		
		// NOTE: from OpenGL documentation, glGenTextures looks to
		// always give good values as long as it is not called
		// between glBegin() and glEnd()
		GL11.glGenTextures(intB);
		return intB.get(0);
	}
}
